//  IDVI 1.0 source copyright 1996 Garth A. Dickie
//
//  This source is free for non-commercial use.  No warranty, etc.
//  Please acknowledge reuse by including the line:
//
//  "Based in part on IDVI 1.0 source copyright 1996 Garth A. Dickie"
//
//  in your documentation and source code.  For commercial use or
//  distribution, please contact the author.  Please also send
//  questions, comments, bug reports, or fixes.
//
//  Best Regards,
//  Garth A. Dickie
//  dickie@elastic.avid.com

package ibook.v10.idvi.display;

import java.awt.Color;
import java.awt.image.IndexColorModel;
import java.util.Hashtable;

import ibook.v10.idvi.IDVI;

//  A ScaledColorScheme object knows about colors to be used at a given
//  scale factor.  It can be used to obtain a ColorModel object, or to
//  obtain individual Color objects.
//
//  The ScaledColorScheme methods often take an index parameter, which
//  selects one of four possible foreground/background pairs.  These
//  are for normal text, link text, link text which is currently being
//  clicked on, and selected text.  (As in Netscape, when a link is
//  selected with other text, it reverts to normal text color).

class ScaledColorScheme {
    //  A ScaledColorScheme object is never constructed directly.
    //  Instead, one is obtained from a ColorScheme object by calling
    //  the ColorScheme.getScaledColorScheme( ) method with an integer
    //  scale factor.  That method then uses the static ScaledColorScheme.
    //  getScaledColorScheme( ) method to obtain the correct ScaledColorScheme
    //  object.

    final private static int    kCacheInitialSize = 23;
    private static Hashtable    cache = new Hashtable( kCacheInitialSize );
    private static ScaledKey    key = new ScaledKey( );

    static synchronized ScaledColorScheme getScaledColorScheme(
            ColorScheme colorScheme, int scale,
            int background, int foreground, int selection, int link, int selectedLink ) {
        
        key.colorScheme = colorScheme;
        key.colorSchemeHashCode = colorScheme.hashCode( );
        key.scale = scale;

        ScaledColorScheme result = ( ScaledColorScheme ) cache.get( key );

        if( result == null ) {
            result = new ScaledColorScheme( colorScheme, scale, background, foreground, selection, link, selectedLink );
            cache.put( key, result );
            key = new ScaledKey( );
        }

        return result;
    }




    //  This private constructor is called by the above getScaledColorScheme
    //  method if a new ScaledColorScheme needs to be constructed.

    final static int    kIndexText         = 0;
    final static int    kIndexSelection    = 1;
    final static int    kIndexLink         = 2;
    final static int    kIndexSelectedLink = 3;
    final static int    kIndexCount        = 4;

    private ColorScheme colorScheme;
    private int         scale;
    private int         scaleSquared;
    private Color[ ]    foreground = new Color[ kIndexCount ];
    private Color[ ]    background = new Color[ kIndexCount ];

    private ScaledColorScheme(
            ColorScheme colorScheme, int scale,
            int background, int foreground, int selection, int link, int selectedLink ) {

        this.colorScheme = colorScheme;
        this.scale = scale;
        this.scaleSquared = scale * scale;

        if( scale > IDVI.kScaleMax || scale < IDVI.kScaleMin )
            throw new Error( "Bad scale factor \"" + scale + "\"" );

        Color backgroundColor   = new Color( background );
        Color foregroundColor   = new Color( foreground );
        Color selectionColor    = new Color( selection );
        Color linkColor         = new Color( link );
        Color selectedLinkColor = new Color( selectedLink );

        this.foreground[ kIndexText         ] = foregroundColor;
        this.foreground[ kIndexSelection    ] = foregroundColor;
        this.foreground[ kIndexLink         ] = linkColor;
        this.foreground[ kIndexSelectedLink ] = selectedLinkColor;

        this.background[ kIndexText         ] = backgroundColor;
        this.background[ kIndexSelection    ] = selectionColor;
        this.background[ kIndexLink         ] = backgroundColor;
        this.background[ kIndexSelectedLink ] = backgroundColor;
    }




    //  The getColorScheme method is used to obtain the unscaled ColorScheme
    //  object that this ScaledColorScheme object was created from.  This
    //  method is used in ColorBlock.getView( ).

    ColorScheme getColorScheme( ) {
        return colorScheme;
    }

    //  The getColor method is used to obtain a color to be used in the
    //  Graphics.setColor( ) method.  It is called from RuleView.paint( ).

    Color getColor( int index, int intensity ) {
        if( intensity == scaleSquared )
            return getForegroundColor( index );
        else if( intensity == 0 )
            return getBackgroundColor( index );
        else
            return getComputedColor( index, intensity );
    }

    Color getForegroundColor( int index ) {
        return foreground[ index ];
    }

    Color getBackgroundColor( int index ) {
        return background[ index ];
    }

    //  Computed colors are cached.  We don't initially construct the cache
    //  Hashtable, because in some contexts (ColorPush specials, for example)
    //  we may be very unlikely to need it.  Even when needed, it may not
    //  have very many entries -- most rules on a page are probably the same
    //  height, and this restricts the number of colors needed.

    final private static int    kColorCacheInitialSize = 5;

    private Hashtable   colorCache = null;
    private ColorKey    colorKey = null;

    private synchronized Color getComputedColor( int index, int intensity ) {
        if( colorCache == null ) {
            colorCache = new Hashtable( kColorCacheInitialSize );
            colorKey = new ColorKey( );
        }

        colorKey.index = index;
        colorKey.intensity = intensity;
        Color result = ( Color ) colorCache.get( colorKey );

        if( result == null ) {
            result = computeColor( index, intensity );
            colorCache.put( key, result );
        }

        return result;
    }




    //  The GetColorModel method is used to obtain a ColorModel for use in
    //  creating Image objects from scaled bitmap data.  It is called from
    //  ScaledCharacter.getImage( ).

    private IndexColorModel[ ]  colorModelCache = new IndexColorModel[ kIndexCount ];

    IndexColorModel getColorModel( int index ) {
        if( colorModelCache[ index ] == null )
            updateColorModelCache( index );
        
        return colorModelCache[ index ];
    }

    private synchronized void updateColorModelCache( int index ) {
        if( colorModelCache[ index ] == null )
            colorModelCache[ index ] = computeColorModel( index );
    }




    //  The computeColor( ) and computeColorModel( ) methods use the same
    //  formulae to compute color values averaging the foreground and background
    //  colors for the given index.  In the current awt, an IndexColorModel
    //  may not contain more than 256 colors; because the limit of scaleMax == 15
    //  that this imposes is reasonable, we just live with it.

    private Color computeColor( int index, int intensity ) {
        int fg = intensity;
        int bg = scaleSquared - intensity;

        int range = scaleSquared;
        int offset = range / 2;

        int red   = ( fg * foreground[ index ].getRed( )   + bg * background[ index ].getRed( )   + offset ) / range;
        int green = ( fg * foreground[ index ].getGreen( ) + bg * background[ index ].getGreen( ) + offset ) / range;
        int blue  = ( fg * foreground[ index ].getBlue( )  + bg * background[ index ].getBlue( )  + offset ) / range;

        return new Color( red, green, blue );
    }

    //  We create an IndexColorModel, with entry number 0 being full-intensity
    //  background (and transparent), entry number scaleSquared equal to full-
    //  intensity foreground, and a smooth gradation in between.

    private IndexColorModel computeColorModel( int index ) {
        int range = scaleSquared;
        int count = range + 1;
        int offset = range / 2;

        byte red  [ ] = new byte[ count ];
        byte green[ ] = new byte[ count ];
        byte blue [ ] = new byte[ count ];

        int foregroundRed   = foreground[ index ].getRed( );
        int foregroundGreen = foreground[ index ].getGreen( );
        int foregroundBlue  = foreground[ index ].getBlue( );

        int backgroundRed   = background[ index ].getRed( );
        int backgroundGreen = background[ index ].getGreen( );
        int backgroundBlue  = background[ index ].getBlue( );

        for( int fg = 0; fg < count; ++ fg ) {
            int bg = range - fg;

            red  [ fg ] = ( byte )(( fg * foregroundRed   + bg * backgroundRed   + offset ) / range );
            green[ fg ] = ( byte )(( fg * foregroundGreen + bg * backgroundGreen + offset ) / range );
            blue [ fg ] = ( byte )(( fg * foregroundBlue  + bg * backgroundBlue  + offset ) / range );
        }

        return new IndexColorModel( 8, count, red, green, blue, 0 );
    }
}




//  The ScaledKey object is used to index the static Hashtable of
//  ScaledColorScheme objects.  The equality test can use pointer
//  equality on the ColorModel component of the key, since ColorModel
//  objects which are distinct are not equal( ).

final class ScaledKey {
    ColorScheme colorScheme;
    int         colorSchemeHashCode;
    int         scale;

    public int hashCode( ) {
        return colorSchemeHashCode ^ scale; 
    }

    public boolean equals( Object object ) {
        return object instanceof ScaledKey && equals(( ScaledKey ) object );
    }

    public boolean equals( ScaledKey key ) {
        return colorScheme == key.colorScheme && scale == key.scale;
    }
}




//  The ColorKey object is used to index the Hashtable of Color objects
//  maintained by each ScaledColorScheme object.

final class ColorKey {
    int index;
    int intensity;

    public int hashCode( ) {
        return ( intensity << 2 ) | index;
    }

    public boolean equals( Object object ) {
        return object instanceof ColorKey && equals(( ColorKey ) object );
    }

    public boolean equals( ColorKey key ) {
        return index == key.index && intensity == key.intensity;
    }
}
